#region Using Statements
using System;
using GameBase.Cameras;
using GameBase.Inputs;
using GameBase.Screens;
using GameBase.Sounds;
using GameBase.Sprites;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using StarFox.Graphics;
using StarFox.Inputs;
using StarFox.ScreenComponents;
using StarFox.Screens;
using StarFox.Sounds;
#if DEBUG
using System.Text;
using Microsoft.Xna.Framework.Input;
using StarFox.Testing;
using System.Diagnostics;
#endif
#endregion

namespace StarFox
{
    /// <summary>
    /// Main class of the game.
    /// </summary>
    public class StarFoxGame : Game
    {
        #region Graphics Management

        // graphics configuration settings
        GraphicsConfig graphicsConfig;

        // graphics device management
        GraphicsDeviceManager graphics;

        // shared SpriteBatch used to draw textures
        SpriteBatch spriteBatch;

        // post-processing
        PostScreenProcessor postScreenProcessor;

        #endregion

        #region Sound Management

        ScreenSound screenSound;
        GamePlaySound gamePlaySound;
        PlayListManager playListManager;

        #endregion

        #region Screen Management

        ScreenManager screenManager;
        ContentManager screenContentManager;

        #endregion

#if DEBUG
        #region Testing

        StringBuilder testInfo;

        // frame rate
        int frameRate;
        int frameCounter;
        TimeSpan elapsedTime;

        #endregion
#endif

        #region Constructor

        /// <summary>
        /// Initializes a new instance of this class.
        /// </summary>
        /// <param name="graphicsConfig">Graphics configuration settings.</param>
        public StarFoxGame(GraphicsConfig graphicsConfig)
        {
            #region Graphics Configuration

            graphics = new GraphicsDeviceManager(this);
            graphics.PreferMultiSampling = true;

            this.graphicsConfig = graphicsConfig;

            #endregion

            #region Display Configuration

            graphics.PreferredBackBufferWidth = graphicsConfig.DisplayWidth;
            graphics.PreferredBackBufferHeight = graphicsConfig.DisplayHeight;
            graphics.IsFullScreen = graphicsConfig.DisplayMode;

            #endregion

            #region Global Content Management

            Content.RootDirectory = "StarFoxContent";

            #endregion
        }

        #endregion

        #region Initialize

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// </summary>
        protected override void Initialize()
        {
            #region Screen Content Management

            Services.AddService(typeof(ContentManager), new ContentManager(Services) { RootDirectory = "StarFoxContent" });

            #endregion

            #region Graphics Management

            spriteBatch = new SpriteBatch(GraphicsDevice);
            Services.AddService(typeof(SpriteBatch), spriteBatch);

            Services.AddService(typeof(GraphicsConfig), graphicsConfig);

            #endregion

            #region Game Settings Management

            Services.AddService(typeof(GameSettings), GameSettings.Instance);
            GameSettings.Instance.LoadSettings();

            #endregion

            #region Game Score Management

            Services.AddService(typeof(GameScore), GameScore.Instance);
            GameScore.Instance.LoadScores();
            GameScore.Instance.Name = Environment.UserName;

            #endregion

            #region Input Management

            StarFoxInput input = new StarFoxInput(InputState.Instance) { MenuThreshold = 0.9f };
            Services.AddService(typeof(IPlayerInput), input);
            Services.AddService(typeof(IShipInput), input);

            Services.AddService(typeof(StarFoxVibration), new StarFoxVibration(VibrationState.Instance));

            #endregion

            #region Sound Management

            screenSound = new ScreenSound(Content.RootDirectory + SoundSettings.GetParameter("Sound_Path"), SoundSettings.GetParameter("Sound_Project"),
                                          SoundSettings.GetParameter("Sound_WaveBank"), SoundSettings.GetParameter("Sound_SoundBank"));

            screenSound.SetVolume((float)GameSettings.Instance.SfxVolLevel / 10.0f);
            Services.AddService(typeof(IScreenSound), screenSound);
            Services.AddService(typeof(ScreenSound), screenSound);

            gamePlaySound = new GamePlaySound(Content.RootDirectory + SoundSettings.GetParameter("Sound_Path"), SoundSettings.GetParameter("Sound_Project"),
                                              SoundSettings.GetParameter("Sound_WaveBank"), SoundSettings.GetParameter("Sound_SoundBank"));

            gamePlaySound.SetVolume((float)GameSettings.Instance.SfxVolLevel / 10.0f);
            Services.AddService(typeof(GamePlaySound), gamePlaySound);

            playListManager = new PlayListManager(Content.RootDirectory + SoundSettings.GetParameter("Music_Path"), SoundSettings.GetParameter("Music_Project"),
                                                  SoundSettings.GetParameter("Music_WaveBank"), SoundSettings.GetParameter("Music_SoundBank"));

            playListManager.SetVolume((float)GameSettings.Instance.MusVolLevel / 10.0f);
            Services.AddService(typeof(IPlayList), playListManager);

            #endregion

            #region Screen Management

            screenManager = new ScreenManager(this) { Time = TimeSpan.FromMilliseconds(750) };
            Components.Add(screenManager);

            #endregion

            #region Camera Management

            Services.AddService(typeof(ICamera), new Camera() { Projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, GraphicsDevice.Viewport.AspectRatio, 0.1f, 10000.0f) });

            #endregion

#if DEBUG
            #region Testing

            Services.AddService(typeof(TestInput), new TestInput(InputState.Instance));

            // write here all testing information
            testInfo = new StringBuilder();
            Services.AddService(typeof(StringBuilder), testInfo);

            #endregion
#endif

            base.Initialize();
        }

        #endregion

        #region LoadContent

        /// <summary>
        /// Called once per game and is the place to load all of the content.
        /// </summary>
        protected override void LoadContent()
        {
            #region Display Processing

            Effect displayEffect = Content.Load<Effect>(ContentSettings.GetAsset("DISPLAY_EFFECT"));
            DisplayProcessor displayProcessor = new DisplayProcessor(displayEffect)
            {
                // ... do it only after game settings loaded
                Brightness = GameSettings.Instance.BrightnessLevel,
                Contrast = GameSettings.Instance.ContrastLevel
            };
            Services.AddService(typeof(DisplayProcessor), displayProcessor);

            #endregion

            #region Post-Screen Processing

            if (graphicsConfig.PostProcessing)
            {
                Effect motionBlurEffect = Content.Load<Effect>(ContentSettings.GetAsset("MOTIONBLUR_EFFECT"));
                Effect postBloomEffect = Content.Load<Effect>(ContentSettings.GetAsset("POSTBLOOM_EFFECT"));
                Texture2D screenBorderTexture = Content.Load<Texture2D>(ContentSettings.GetAsset("SCREENBORDER_FADEOUT"));
                postScreenProcessor = new PostScreenProcessor(motionBlurEffect, postBloomEffect, screenBorderTexture);
                Services.AddService(typeof(PostScreenProcessor), postScreenProcessor);
            }

            #endregion

            #region Global Content Loading

            // fonts
            Content.Load<SpriteFont>(ContentSettings.GetAsset("FONT_SMALL"));
            Content.Load<SpriteFont>(ContentSettings.GetAsset("FONT_BIG"));

            // menu sprites
            Content.Load<SpriteSheet>(ContentSettings.GetAsset("MENU_SPRITES"));

            // menu background
            Content.Load<Effect>(ContentSettings.GetAsset("BACKGROUND_EFFECT"));
            Content.Load<Texture2D>(ContentSettings.GetAsset("BACKGROUND_TEXTURE"));

#if DEBUG
            Content.Load<SpriteFont>(ContentSettings.GetAsset("FONT_TEST"));
#endif

            #endregion

            #region Per-Screen Content Loading

            LoadingScreen.Load(screenManager, new PegiScreen(screenManager), LoadTime.FAST,
                   (contentManager, assetIndex) =>
                   {
                       if (assetIndex == 1)
                           return true;

                       switch (assetIndex)
                       {
                           case 0:
                               contentManager.Load<SpriteSheet>(ContentSettings.GetAsset("INTRO_SPRITES"));
                               break;
                           default:
                               break;
                       }

                       return false;
                   });

            #endregion
        }

        #endregion

        #region UnloadContent

        /// <summary>
        /// Called once per game and is the place to unload all content.
        /// </summary>
        protected override void UnloadContent()
        {
            if (spriteBatch != null) { spriteBatch.Dispose(); spriteBatch = null; }
            if (postScreenProcessor != null) { postScreenProcessor.Dispose(); postScreenProcessor = null; }
            if (screenSound != null) { screenSound.Dispose(); screenSound = null; }
            if (gamePlaySound != null) { gamePlaySound.Dispose(); gamePlaySound = null; }
            if (playListManager != null) { playListManager.Dispose(); playListManager = null; }
            if (screenContentManager != null) { screenContentManager.Dispose(); screenContentManager = null; }
        }

        #endregion

        #region Update

        /// <summary>
        /// Called when the game needs to be updated.
        /// </summary>
        /// <param name="gameTime">Time elapsed since the last call to this method.</param>
        protected override void Update(GameTime gameTime)
        {
            // update player input
            InputState.Instance.Update();
            VibrationState.Instance.Enabled = GameSettings.Instance.VibrationEnabled;
            VibrationState.Instance.Update(gameTime);

            // update sound managers
            screenSound.Update(gameTime);
            gamePlaySound.Update(gameTime);
            playListManager.Update(gameTime);

#if DEBUG
            // allows the game to exit
            if (Keyboard.GetState().IsKeyDown(Keys.Delete))
                this.Exit();

            // write new testing information each frame
            testInfo.Clear();

            #region Frame Rate

            elapsedTime += gameTime.ElapsedGameTime;

            if (elapsedTime > TimeSpan.FromSeconds(1))
            {
                elapsedTime -= TimeSpan.FromSeconds(1);
                frameRate = frameCounter;
                frameCounter = 0;
            }

            if (frameRate < 10)
                testInfo.AppendFormat("0{0} FPS\n\n", frameRate.ToString());
            else
                testInfo.AppendFormat("{0} FPS\n\n", frameRate.ToString());

            #endregion
#endif

            // update all game components
            base.Update(gameTime);
        }

        #endregion

        #region Draw

        /// <summary>
        /// Called when the game needs to be drawn.
        /// </summary>
        /// <param name="gameTime">Time elapsed since the last call to this method.</param>
        protected override void Draw(GameTime gameTime)
        {
#if DEBUG
            frameCounter++;
#endif

            GraphicsDevice.Clear(Color.Black);

            // draw all game components
            base.Draw(gameTime);
        }

        #endregion

        #region Activated, Deactivated

        /// <summary>
        /// Raises the Activated event, which is raised when the game gains focus.
        /// </summary>
        /// <param name="sender">The Game.</param>
        /// <param name="args">Arguments for the Activated event.</param>
        protected override void OnActivated(object sender, EventArgs args)
        {
            // exception thrown in release mode??
            try
            {
                // resume music and sounds
                screenSound.PlayAllSounds();
                gamePlaySound.PlayAllSounds();
                playListManager.Play();
            }
            catch { }

            base.OnActivated(sender, args);
        }

        /// <summary>
        /// Raises the Deactivated event, which is raised when the game loses focus.
        /// </summary>
        /// <param name="sender">The Game.</param>
        /// <param name="args">Arguments for the Deactivated event.</param>
        protected override void OnDeactivated(object sender, EventArgs args)
        {
            // pause music and sounds
            screenSound.PauseAllSounds();
            gamePlaySound.PauseAllSounds();
            playListManager.Pause();

            base.OnDeactivated(sender, args);
        }

        #endregion

        #region Exiting

        /// <summary>
        /// Raises an Exiting event.
        /// </summary>
        /// <param name="sender">The Game.</param>
        /// <param name="args">Arguments for the Exiting event.</param>
        protected override void OnExiting(object sender, EventArgs args)
        {
            // save to file modified game settings in this game...
            // ... so they'll be available for the next game
            GameSettings.Instance.SaveSettings();

            // save game scores achieved by the player...
            // ... so they'll be available for the next game
            GameScore.Instance.SaveScores();

            base.OnExiting(sender, args);
        }

        #endregion
    }
}
